Google Protocol BUffers使用

简介

Google Protocol Buffer简称Protobuf,是由Google开发的用来序列化、反序列化数据结构的技术,它支持C++、Java、python等多种语言。

  • 安装

    下载源码:点击这里
    安装:
    1
    2
    3
    4
    ./configure --prefix=$INSTALL_DIR 
    make
    make check
    make install

一个例子

使用Protobuf通常需要:
1、定义消息格式。
2、生成.h和.cc文件
3、使用Protobuf提供的API写代码

定义消息格式

消息格式定义文件一般以.proto后缀结尾,可以在.proto文件中添加想要序列化的消息。一个消息由类型和消息名组成。下面是addressbook.proto消息定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package tutorial;

message Person {
required string name = 1;
required int32 id = 2;
optional string email = 3;

enum PhoneType {
MOBILE = 0;
HOME = 1;
WORK = 2;
}

message PhoneNumber {
required string number = 1;
optional PhoneType type = 2 [default = HOME];
}

repeated PhoneNumber phone = 4;
}

message AddressBook {
repeated Person person = 1;
}

上面一package开头,作用了命名空间类似,可以防止命名冲突。
消息定义有类型和消息名组成,类型包括bool, int32, float, double, string。消息名后面的括号[]中default=表示这个消息的默认值。
消息后面会后=1,=2这样的字段,这是用来标记消息在二进制编码中的唯一性的。编号1-15编码占用空间小于1字节,为了优化考虑,经常用到的字段建议使用编号1-15。
每个消息类型前面还有个修饰符:
required:表示必须提供初始值,否则这个消息字段是未初始化的。
optional:表示如果没有提供初始值,那么可以指定默认值。
repeated:表示该消息字段可能会重复,出现多次或者0次。

生成.h和.cc文件

使用命令

1
protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto

生成addressbook.pb.cc addressbook.pb.h文件

对应API

可以在addressbook.pb.h中拿出一部分代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// required string name = 1;
inline bool has_name() const;
inline void clear_name();
static const int kNameFieldNumber = 1;
inline const ::std::string& name() const;
inline void set_name(const ::std::string& value);
inline void set_name(const char* value);
inline void set_name(const char* value, size_t size);
inline ::std::string* mutable_name();
inline ::std::string* release_name();
inline void set_allocated_name(::std::string* name);

// required int32 id = 2;
inline bool has_id() const;
inline void clear_id();
static const int kIdFieldNumber = 2;
inline ::google::protobuf::int32 id() const;
inline void set_id(::google::protobuf::int32 value);

// optional string email = 3;
inline bool has_email() const;
inline void clear_email();
static const int kEmailFieldNumber = 3;
inline const ::std::string& email() const;
inline void set_email(const ::std::string& value);
inline void set_email(const char* value);
inline void set_email(const char* value, size_t size);
inline ::std::string* mutable_email();
inline ::std::string* release_email();
inline void set_allocated_email(::std::string* email);

// repeated .tutorial.Person.PhoneNumber phone = 4;
inline int phone_size() const;
inline void clear_phone();
static const int kPhoneFieldNumber = 4;
inline const ::tutorial::Person_PhoneNumber& phone(int index) const;
inline ::tutorial::Person_PhoneNumber* mutable_phone(int index);
inline ::tutorial::Person_PhoneNumber* add_phone();

可以看出,对于requiredoptional字段,都有set_has_方法,前者用来设置字段值,后者用来检验是否设置了值。所有字段都有clear_方法,用来清除设置的值。
对于repeated字段,有_size()方法,用来检查有多少个这样的字段。
可以通过字段名()的方法来获取字段的值,但要注意返回的是常引用。要想修改字段的是,通过mutable_字段名()来获取指向字段的指针。

###序列化和解析
每个protobuf类都有API来序列化消息为二进制或解析二进制消息:
bool SerializeToString(string* output) const;: 把消息序列化为二进制的string
bool ParseFromString(const string& data);: 解析二进制的string消息
bool SerializeToOstream(ostream* output) const;: 把消息序列化为二进制输出流。
bool ParseFromIstream(istream* input);: 从二进制输入流解析消息。

实战

二进制文件的读写

写消息,把消息序列化,写到二进制文件中,保存到硬盘。
writer.cpp把消息序列化写到硬盘,reader.cpp从硬盘读取消息并打印。注意编译时要链接protobuf库,使用选线-lprotobuf。

writer.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <fstream>
#include <string>
#include "addressbook.pb.h"

using std::string;
int main()
{

tutorial::AddressBook address_book;
//添加一个用户信息
tutorial::Person* person = address_book.add_person();
person->set_name("xiaoming");
person->set_id(1);

tutorial::Person::PhoneNumber* phone_number = person->add_phone();
phone_number->set_number("13812345678");
phone_number->set_type(tutorial::Person::MOBILE);

//再添加一个用户
person = address_book.add_person();
person->set_name("xiaosan");
person->set_id(2);
person->set_email("xiaosan@email.com");
phone_number = person->add_phone();
phone_number->set_number("02012345678");
phone_number->set_type(tutorial::Person::HOME);

//保存为文本
{
std::fstream output("./addressbook.bin", std::ios::out | std::ios::binary | std::ios::trunc);
if (!address_book.SerializeToOstream(&output)) {
std::cerr << "Failed to parse address book." << std::endl;
return -1;
}
}
return 0;
}

reader.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <iostream>
#include <fstream>
#include <string>

#include "addressbook.pb.h"

using namespace std;

int main()
{

tutorial::AddressBook address_book;
{
fstream input("./addressbook.bin", ios::in | ios::binary);
if(!address_book.ParseFromIstream(&input))
{
cerr<<"Failed to parse address book."<<endl;
return -1;
}
}

//输出address_book信息
for(int i = 0; i< address_book.person_size(); ++i)
{
const tutorial::Person& person = address_book.person(i);
cout<<"Person ID: "<<person.id()<<endl;
cout<<" Name: "<<person.name()<<endl;
if( person.has_email())
{
cout<<" E-mail address: "<<person.email()<<endl;
}
for( int j = 0; j < person.phone_size(); ++j)
{
const tutorial::Person::PhoneNumber& phone_number = person.phone(j);

switch(phone_number.type())
{
case tutorial::Person::MOBILE:
cout<<" Mobile phone #";
break;
case tutorial::Person::HOME:
cout<<" Home phone #";
break;
case tutorial::Person::WORK:
cout<<" Work phone #";
break;
}
cout<< phone_number.number()<<endl;
}
}
}

文本文件的读写

protobuf可以以文本形式保存到文本中,也可以从文中读取消息。配置文件可以以protobuf文本形式保存使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <iostream>
#include <fstream>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <google/protobuf/io/coded_stream.h>
#include <google/protobuf/io/zero_copy_stream_impl.h>
#include <google/protobuf/text_format.h>

#include "addressbook.pb.h"

using namespace std;

using google::protobuf::io::FileInputStream;
using google::protobuf::io::FileOutputStream;
using google::protobuf::Message;
//从文本读到protobuf中
bool ReadProtoFromTextFile(const char* filename, Message* proto) {
int fd = open(filename, O_RDONLY);
FileInputStream* input = new FileInputStream(fd);
bool success = google::protobuf::TextFormat::Parse(input, proto);
delete input;
close(fd);
return success;
}
//写到protobuf中
void WriteProtoToTextFile(const Message& proto, const char* filename) {
int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644);
FileOutputStream* output = new FileOutputStream(fd);
google::protobuf::TextFormat::Print(proto, output);
delete output;
close(fd);
}

int main()
{

tutorial::AddressBook address_book;
{
fstream input("./addressbook.bin", ios::in | ios::binary);
if(!address_book.ParseFromIstream(&input))
{
cerr<<"Failed to parse address book."<<endl;
return -1;
}
}

//写到文本中
WriteProtoToTextFile(address_book, "./addressbook.txt");
return 0;

}

参考:
Google Protocol Buffer 的使用和原理
Protocol Buffer Basics: C++

文章目录
  1. 1. 简介
  2. 2. 安装
  3. 3. 一个例子
    1. 3.1. 定义消息格式
    2. 3.2. 生成.h和.cc文件
    3. 3.3. 对应API
  4. 4. 实战
    1. 4.1. 二进制文件的读写
    2. 4.2. 文本文件的读写
,
#add by kangyabing